General Set UP

Library

library(dplyr)
library(readr)
library(broom)
library(ggplot2)
library(tidymodels) 
library(probably)
library(vip)
library(plotly)
library(ClusterR)
library(cluster)
library(vip)
tidymodels_prefer()
theme_set(theme_bw())       
Sys.setlocale("LC_TIME", "English")
## [1] "English_United States.1252"
set.seed(74)

Read in data

breastCa<-read_csv(file = "breast-cancer.csv")

Regression

Data cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(radius_mean:fractal_dimension_mean) 

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-8)

Creation of cv folds

breastCa_Re_CV<-vfold_cv(breastCa_Re, v = 10)

Model spec

#least square
lm_spec <-
    linear_reg() %>% 
    set_engine(engine = 'lm') %>% 
    set_mode('regression')

#LASSO
lm_lasso_spec <- 
  linear_reg() %>%
  set_args(mixture = 1, penalty = tune()) %>% ## mixture = 1 indicates Lasso
  set_engine(engine = 'glmnet') %>% #note we are using a different engine
  set_mode('regression')

Recipes & workflows

#least square
least_rec <- recipe(area_mean ~ ., data = breastCa_Re) %>%
    step_corr(all_predictors()) %>% 
    step_nzv(all_predictors()) %>% # removes variables with the same value
    step_normalize(all_numeric_predictors()) %>% # important standardization step for LASSO
    step_dummy(all_nominal_predictors())

least_lm_wf <- workflow() %>%
    add_recipe(least_rec) %>%
    add_model(lm_spec)
    
#LASSO
lasso_wf<- workflow() %>% 
  add_recipe(least_rec) %>%
  add_model(lm_lasso_spec) 

Fit & tune models

#least square
least_fit <- fit(least_lm_wf, data = breastCa_Re) 

least_fit %>% tidy()
#LASSO
#tune
penalty_grid <- grid_regular(
  penalty(range = c(-3, 1)), #log10 transformed 
  levels = 30)

tune_output <- tune_grid( # new function for tuning hyperparameters
  lasso_wf, # workflow
  resamples = breastCa_Re_CV, # cv folds
  metrics = metric_set(rmse, mae),
  grid = penalty_grid # penalty grid defined above
)

#fit
best_se_penalty <- select_by_one_std_err(tune_output, metric = 'mae', desc(penalty))
final_wf_se <- finalize_workflow(lasso_wf, best_se_penalty)
lasso_fit <- fit(final_wf_se , data = breastCa_Re)
lasso_fit %>% tidy()

Calculate and collect CV metrics

# Least Square model
least_fit_cv <- fit_resamples(least_lm_wf,
  resamples = breastCa_Re_CV, 
  metrics = metric_set(rmse, mae)
)

least_fit_cv %>% collect_metrics(summarize = TRUE)
# LASSO model
tune_output %>% 
  collect_metrics() %>% 
  filter(penalty == (best_se_penalty 
                     %>% pull(penalty)))

Residual Plots

#least square
least_fit_output <- least_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(least_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(least_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

#LASSO
lasso_fit_output <- lasso_fit %>%
  predict(new_data = breastCa_Re) %>%
  bind_cols(breastCa_Re) %>%
  mutate(resid = area_mean - .pred)

ggplot(lasso_fit_output, aes(x = .pred, y = resid)) +
  geom_point() +
  geom_smooth() +
  geom_hline(yintercept = 0, color = "red") +
  labs(x = "Fitted values", y = "Residuals") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = concavity_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Concavity mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = compactness_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Compactness mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = fractal_dimension_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Fractal dimension mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

# Residuals vs. predictors (x's) 
ggplot(lasso_fit_output, aes(x = radius_mean, y = resid)) +
  geom_point() +
  geom_smooth() +
  labs(x = "Radius mean", y = "Residual") +
  geom_hline(yintercept = 0, color = "red") +
  theme_classic()

Accounting for nonlinearity

set.seed(123)

gam_spec <- 
  gen_additive_mod() %>%
  set_engine(engine = 'mgcv') %>%
  set_mode('regression') 

gam_mod <- fit(gam_spec,
    area_mean ~ s(radius_mean)+texture_mean+s(perimeter_mean)+smoothness_mean+compactness_mean+concave_points_mean+concavity_mean+symmetry_mean+fractal_dimension_mean,
    data = breastCa_Re_new
)

par(mfrow=c(2,2))
gam_mod %>% pluck('fit') %>% mgcv::gam.check() 

## 
## Method: GCV   Optimizer: magic
## Smoothing parameter selection converged after 19 iterations.
## The RMS GCV score gradient at convergence was 3.693889e-05 .
## The Hessian was positive definite.
## Model rank =  26 / 26 
## 
## Basis dimension (k) checking results. Low p-value (k-index<1) may
## indicate that k is too low, especially if edf is close to k'.
## 
##                     k'  edf k-index p-value  
## s(radius_mean)    9.00 8.73    0.91   0.015 *
## s(perimeter_mean) 9.00 9.00    0.96   0.170  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
gam_mod %>% pluck('fit') %>% summary()
## 
## Family: gaussian 
## Link function: identity 
## 
## Formula:
## area_mean ~ s(radius_mean) + texture_mean + s(perimeter_mean) + 
##     smoothness_mean + compactness_mean + concave_points_mean + 
##     concavity_mean + symmetry_mean + fractal_dimension_mean
## 
## Parametric coefficients:
##                         Estimate Std. Error t value Pr(>|t|)    
## (Intercept)             676.8849     9.4284  71.792  < 2e-16 ***
## texture_mean              0.2863     0.1136   2.521 0.012000 *  
## smoothness_mean         176.4116    55.3592   3.187 0.001522 ** 
## compactness_mean       -559.1059    41.8880 -13.348  < 2e-16 ***
## concave_points_mean    -193.1237    55.0443  -3.509 0.000488 ***
## concavity_mean           75.1818    19.7185   3.813 0.000153 ***
## symmetry_mean            24.5876    21.8517   1.125 0.261001    
## fractal_dimension_mean  193.2815   164.7137   1.173 0.241134    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Approximate significance of smooth terms:
##                     edf Ref.df     F p-value    
## s(radius_mean)    8.726  8.982 79.72  <2e-16 ***
## s(perimeter_mean) 9.000  9.000 36.36  <2e-16 ***
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## R-sq.(adj) =  0.999   Deviance explained = 99.9%
## GCV = 115.47  Scale est. = 110.25    n = 569
ns_rec <- least_rec %>%
  step_ns(x, deg_free = 9)
ns9_wf <- workflow()  %>%
  add_recipe(ns_rec) %>%
  add_model(lm_spec)

hist(breastCa_Re_new$area_mean)

gam_mod %>% pluck('fit') %>% plot()

Evaluation of the GAM model on Test Data

gam_test_output <- gam_mod %>%
  predict(new_data=breastCa_Re_new) %>%
  bind_cols(breastCa_Re_new %>% select(area_mean))

gam_test_output %>%
  rmse(truth=area_mean, estimate=.pred) 
gam_test_output %>%
  mae(truth=area_mean, estimate=.pred) 

Classification

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10)

LASSO and Logistic Regression

Implete Lasso Logistic Regression in tidymodels

# Make sure you set reference level (to the outcome you are NOT interested in)
breastCa_Re_new2 <- breastCa_Re_new%>%
  mutate(diagnosis = relevel(factor(diagnosis ), ref='B')) #set reference level

data_cv10 <- vfold_cv(breastCa_Re_new2, v = 10)


# Logistic LASSO Regression Model Spec
logistic_lasso_spec_tune <- logistic_reg() %>%
    set_engine('glmnet') %>%
    set_args(mixture = 1, penalty = tune()) %>%
    set_mode('classification')

# Recipe
logistic_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2) %>%
    step_normalize(all_numeric_predictors()) %>% 
    step_dummy(all_nominal_predictors())

# Workflow (Recipe + Model)
log_lasso_wf <- workflow() %>% 
    add_recipe(logistic_rec) %>%
    add_model(logistic_lasso_spec_tune) 

# Tune Model (trying a variety of values of Lambda penalty)
penalty_grid <- grid_regular(
  penalty(range = c(-5, 1)), #log10 transformed  (kept moving min down from 0)
  levels = 100)

tune_output <- tune_grid( 
  log_lasso_wf, # workflow
  resamples = data_cv10, # cv folds
  metrics = metric_set(roc_auc,accuracy),
  control = control_resamples(save_pred = TRUE, event_level = 'second'),
  grid = penalty_grid # penalty grid defined above
)

# Visualize Model Evaluation Metrics from Tuning
autoplot(tune_output) + theme_classic()

Inspecting the Model

best_se_penalty <- select_by_one_std_err(tune_output, metric = 'roc_auc', desc(penalty)) # choose penalty value based on the largest penalty within 1 se of the highest CV roc_auc
final_fit_se <- finalize_workflow(log_lasso_wf, best_se_penalty) %>% # incorporates penalty value to workflow 
    fit(data = breastCa_Re_new2)

final_fit_se %>% tidy()
final_fit_se %>% tidy() %>%
  filter(estimate == 0)
#variable importance
glmnet_output <- final_fit_se %>% extract_fit_engine()
    
# Create a boolean matrix (predictors x lambdas) of variable exclusion
bool_predictor_exclude <- glmnet_output$beta==0

# Loop over each variable
var_imp <- sapply(seq_len(nrow(bool_predictor_exclude)), function(row) {
    # Extract coefficient path (sorted from highest to lowest lambda)
    this_coeff_path <- bool_predictor_exclude[row,]
    # Compute and return the # of lambdas until this variable is out forever
    ncol(bool_predictor_exclude) - which.min(this_coeff_path) + 1
})

# Create a dataset of this information and sort
var_imp_data <- tibble(
    var_name = rownames(bool_predictor_exclude),
    var_imp = var_imp
)
var_imp_data %>% arrange(desc(var_imp))

Evaluation Metrics

# CV results for "best lambda"
tune_output %>%
    collect_metrics() %>%
    filter(penalty == best_se_penalty %>% pull(penalty))
# Count up number of B and M in the training data
breastCa_Re_new2 %>%
    count(diagnosis) # Name of the outcome variable goes inside count()
#Compute the NIR
NIR<- 357/(357+212)
NIR
## [1] 0.6274165

Threshold

# Soft Predictions on Training Data
final_output <-
  final_fit_se %>% predict(new_data = breastCa_Re_new2, type = 'prob') %>%     bind_cols(breastCa_Re_new2)



final_output %>%
  ggplot(aes(x = diagnosis, y = .pred_M)) +
  geom_boxplot()

# Use soft predictions
final_output %>%
    roc_curve(diagnosis,.pred_M,event_level = 'second') %>%
    autoplot()

# thresholds in terms of reference level
threshold_output <- final_output %>%
    threshold_perf(truth = diagnosis, estimate = .pred_B, thresholds = seq(0,1,by=.01)) 

# J-index v. threshold for not M
threshold_output %>%
    filter(.metric == 'j_index') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'J-index', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'j_index') %>%
    arrange(desc(.estimate))
# Distance v. threshold for not M

threshold_output %>%
    filter(.metric == 'distance') %>%
    ggplot(aes(x = .threshold, y = .estimate)) +
    geom_line() +
    labs(y = 'Distance', x = 'threshold') +
    theme_classic()

threshold_output %>%
    filter(.metric == 'distance') %>%
    arrange(.estimate)
log_metrics <- metric_set(accuracy,sens,yardstick::spec)

final_output %>%
    mutate(.pred_class = make_two_class_pred(.pred_B, levels(diagnosis), threshold = .64)) %>%
    log_metrics(truth = diagnosis, estimate = .pred_class, event_level = 'second')

Random Forest

Building Random Forest

# Model Specification
rf_spec <- rand_forest() %>%
  set_engine(engine = 'ranger') %>% 
  set_args(mtry = NULL, # size of random subset of variables; default is floor(sqrt(ncol(x)))
           trees = 1000, # Number of trees
           min_n = 2,
           probability = FALSE, # FALSE: hard predictions
           importance = 'impurity') %>% 
  set_mode('classification') # change this for regression tree

# Recipe
data_rec <- recipe(diagnosis ~ ., data = breastCa_Re_new2)

# Workflows
data_wf_mtry2 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 2)) %>%
  add_recipe(data_rec)

# Create workflows for mtry = 4 , 10, and 20
data_wf_mtry4 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 4)) %>%
  add_recipe(data_rec)

data_wf_mtry10 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 10)) %>%
  add_recipe(data_rec)

data_wf_mtry20 <- workflow() %>%
  add_model(rf_spec %>% set_args(mtry = 20)) %>%
  add_recipe(data_rec)
# Fit Models

set.seed(123) # make sure to run this before each fit so that you have the same 1000 trees
data_fit_mtry2 <- fit(data_wf_mtry2, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry4 <- fit(data_wf_mtry4, data = breastCa_Re_new2)

set.seed(123) 
data_fit_mtry10 <- fit(data_wf_mtry10, data = breastCa_Re_new2)

set.seed(123)
data_fit_mtry20 <- fit(data_wf_mtry20, data = breastCa_Re_new2)
# Custom Function to get OOB predictions, true observed outcomes and add a model label
rf_OOB_output <- function(fit_model, model_label, truth){
    tibble(
          .pred_diagnosis = fit_model %>% extract_fit_engine() %>% pluck('predictions'), #OOB predictions
          diagnosis = truth,
          model = model_label
      )
}

#check out the function output
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis))
# Evaluate OOB Metrics

data_rf_OOB_output <- bind_rows(
    rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry4,'mtry4', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry10,'mtry10', breastCa_Re_new2 %>% pull(diagnosis)),
    rf_OOB_output(data_fit_mtry20,'mtry20', breastCa_Re_new2 %>% pull(diagnosis))
)


data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate = .pred_diagnosis)

Preliminary interpretation

data_rf_OOB_output %>% 
    group_by(model) %>%
    accuracy(truth = diagnosis, estimate =.pred_diagnosis) %>%
  mutate(mtry = as.numeric(stringr::str_replace(model,'mtry',''))) %>%
  ggplot(aes(x = mtry, y = .estimate )) + 
  geom_point() +
  geom_line() +
  theme_classic()

Evaluating the forest

data_fit_mtry2
## == Workflow [trained] ==========================================================
## Preprocessor: Recipe
## Model: rand_forest()
## 
## -- Preprocessor ----------------------------------------------------------------
## 0 Recipe Steps
## 
## -- Model -----------------------------------------------------------------------
## Ranger result
## 
## Call:
##  ranger::ranger(x = maybe_data_frame(x), y = y, mtry = min_cols(~2,      x), num.trees = ~1000, min.node.size = min_rows(~2, x), probability = ~FALSE,      importance = ~"impurity", num.threads = 1, verbose = FALSE,      seed = sample.int(10^5, 1)) 
## 
## Type:                             Classification 
## Number of trees:                  1000 
## Sample size:                      569 
## Number of independent variables:  20 
## Mtry:                             2 
## Target node size:                 2 
## Variable importance mode:         impurity 
## Splitrule:                        gini 
## OOB prediction error:             3.51 %
rf_OOB_output(data_fit_mtry2,'mtry2', breastCa_Re_new2 %>% pull(diagnosis)) %>%
    conf_mat(truth = diagnosis, estimate= .pred_diagnosis)
##           Truth
## Prediction   B   M
##          B 351  14
##          M   6 198

Variable importance measures

data_fit_mtry2 %>% 
    extract_fit_engine() %>% 
    vip(num_features = 30) + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = area_worst)) +
    geom_violin() + theme_classic()

ggplot(breastCa_Re_new2, aes(x = diagnosis, y = fractal_dimension_mean)) +
    geom_violin() + theme_classic()

#intermediate important
ggplot(breastCa_Re_new2, aes(x = diagnosis, y = perimeter_mean)) +
    geom_violin() + theme_classic()

Clustering

Data Cleaning

breastCa_Re<-breastCa %>% 
  drop_na() %>% 
  select(-c(13:22)) %>% 
  select(-1)

breastCa_Re_new<-breastCa_Re%>%
  mutate(concave_points_mean=`concave points_mean`)%>%
  select(-10) 
ggplot(breastCa_Re_new, aes(x = perimeter_mean, y = `concave points_worst`)) +
    geom_point() +
    theme_classic()

K-means clustering on perimeter_mean and concave points_worst

# Select just the perimeter_mean and concave points_worst variables
breastCa_Re_new_sub <- breastCa_Re_new %>%
    select(perimeter_mean, `concave points_worst`)

# Run k-means for k = centers = 2
set.seed(253)
kclust_k2 <- kmeans(breastCa_Re_new_sub, centers = 2)

# Display the cluster assignments
kclust_k2$cluster
##   [1] 2 2 2 1 2 1 2 1 1 1 2 2 2 2 1 1 1 2 2 1 1 1 2 2 2 2 1 2 2 2 2 1 2 2 2 2 1
##  [38] 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1
##  [75] 1 2 1 2 2 1 1 1 2 2 1 2 1 2 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1
## [112] 1 1 1 1 1 1 1 2 2 1 2 2 1 1 1 1 2 1 2 1 1 2 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1
## [149] 1 1 1 1 1 1 1 1 2 2 1 1 1 2 2 1 2 1 1 2 2 1 1 1 2 1 1 1 1 2 1 1 2 2 1 1 1
## [186] 1 2 1 1 1 1 1 1 1 1 1 1 2 2 1 1 2 2 1 1 1 1 2 1 1 2 1 2 2 1 1 1 1 2 2 1 1
## [223] 1 2 1 1 1 1 1 1 2 1 1 2 1 1 2 2 1 2 1 1 1 1 2 1 1 1 1 1 2 1 2 2 2 1 2 2 2
## [260] 2 2 2 2 1 2 2 1 1 1 1 1 1 2 1 2 1 1 2 1 1 2 1 2 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 1 2 1 2 1 1 1 1 2 2 2 1 1
## [334] 1 1 2 1 2 1 2 1 1 1 2 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 2 2
## [371] 2 1 2 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 2 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 2
## [408] 1 2 1 1 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 2 1 2 2 1 1 1 1 1 1 1 2 1 1
## [445] 2 1 2 1 1 2 1 2 1 1 1 1 1 1 1 1 2 2 1 1 1 1 1 1 2 1 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 2 1 1 2 1 2 1 2 2 1 1 1 1 1 2 2 1 1 1 2 1 1 1 1 2 2 1 1 1 1 1 1 2 2
## [519] 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 2 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 2 2 2 2 2 1
# Add a variable (kclust_k2) to the original dataset 
# containing the cluster assignments
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2 = factor(kclust_k2$cluster))
# Visualize the cluster assignments on the original scatterplot
originalClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(originalClusterPlot  , tooltip = c( "text"))

Addressing variable scale

# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k2_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 2)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_2_scale = factor(kclust_k2_scale$cluster))

# Visualize the new cluster assignments
scaledClusterPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_2,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(scaledClusterPlot  , tooltip = c( "text"))

Clustering on more variables

# Select the variables to be used in clustering
breastCa_Re_new_sub2 <- breastCa_Re_new %>%
    select(c(2:21))

# Look at summary statistics of the 3 variables
summary(breastCa_Re_new_sub2)
##   radius_mean      texture_mean   perimeter_mean     area_mean     
##  Min.   : 6.981   Min.   : 9.71   Min.   : 43.79   Min.   : 143.5  
##  1st Qu.:11.700   1st Qu.:16.17   1st Qu.: 75.17   1st Qu.: 420.3  
##  Median :13.370   Median :18.84   Median : 86.24   Median : 551.1  
##  Mean   :14.127   Mean   :19.29   Mean   : 91.97   Mean   : 654.9  
##  3rd Qu.:15.780   3rd Qu.:21.80   3rd Qu.:104.10   3rd Qu.: 782.7  
##  Max.   :28.110   Max.   :39.28   Max.   :188.50   Max.   :2501.0  
##  smoothness_mean   compactness_mean  concavity_mean    concave points_mean
##  Min.   :0.05263   Min.   :0.01938   Min.   :0.00000   Min.   :0.00000    
##  1st Qu.:0.08637   1st Qu.:0.06492   1st Qu.:0.02956   1st Qu.:0.02031    
##  Median :0.09587   Median :0.09263   Median :0.06154   Median :0.03350    
##  Mean   :0.09636   Mean   :0.10434   Mean   :0.08880   Mean   :0.04892    
##  3rd Qu.:0.10530   3rd Qu.:0.13040   3rd Qu.:0.13070   3rd Qu.:0.07400    
##  Max.   :0.16340   Max.   :0.34540   Max.   :0.42680   Max.   :0.20120    
##  fractal_dimension_mean  radius_worst   texture_worst   perimeter_worst 
##  Min.   :0.04996        Min.   : 7.93   Min.   :12.02   Min.   : 50.41  
##  1st Qu.:0.05770        1st Qu.:13.01   1st Qu.:21.08   1st Qu.: 84.11  
##  Median :0.06154        Median :14.97   Median :25.41   Median : 97.66  
##  Mean   :0.06280        Mean   :16.27   Mean   :25.68   Mean   :107.26  
##  3rd Qu.:0.06612        3rd Qu.:18.79   3rd Qu.:29.72   3rd Qu.:125.40  
##  Max.   :0.09744        Max.   :36.04   Max.   :49.54   Max.   :251.20  
##    area_worst     smoothness_worst  compactness_worst concavity_worst 
##  Min.   : 185.2   Min.   :0.07117   Min.   :0.02729   Min.   :0.0000  
##  1st Qu.: 515.3   1st Qu.:0.11660   1st Qu.:0.14720   1st Qu.:0.1145  
##  Median : 686.5   Median :0.13130   Median :0.21190   Median :0.2267  
##  Mean   : 880.6   Mean   :0.13237   Mean   :0.25427   Mean   :0.2722  
##  3rd Qu.:1084.0   3rd Qu.:0.14600   3rd Qu.:0.33910   3rd Qu.:0.3829  
##  Max.   :4254.0   Max.   :0.22260   Max.   :1.05800   Max.   :1.2520  
##  concave points_worst symmetry_worst   fractal_dimension_worst
##  Min.   :0.00000      Min.   :0.1565   Min.   :0.05504        
##  1st Qu.:0.06493      1st Qu.:0.2504   1st Qu.:0.07146        
##  Median :0.09993      Median :0.2822   Median :0.08004        
##  Mean   :0.11461      Mean   :0.2901   Mean   :0.08395        
##  3rd Qu.:0.16140      3rd Qu.:0.3179   3rd Qu.:0.09208        
##  Max.   :0.29100      Max.   :0.6638   Max.   :0.20750        
##  concave_points_mean
##  Min.   :0.00000    
##  1st Qu.:0.02031    
##  Median :0.03350    
##  Mean   :0.04892    
##  3rd Qu.:0.07400    
##  Max.   :0.20120
set.seed(253)
kclust_k2_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 2)

breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k2_allvars = factor(kclust_k2_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k2_allvars)

Interpreting the clusters

breastCa_Re_new %>%
    group_by(kclust_k2_allvars) %>%
    summarize(across(c(2:21), mean))

Picking k

# Data-specific function to cluster and calculate total within-cluster SS
breastCa_Re_new_cluster_ss <- function(k){
    # Perform clustering
    kclust <- kmeans(scale(breastCa_Re_new_sub2), centers = k)

    # Return the total within-cluster sum of squares
    return(kclust$tot.withinss)
}

tibble(
    k = 1:20,
    tot_wc_ss = purrr::map_dbl(1:20, breastCa_Re_new_cluster_ss)
) %>% 
    ggplot(aes(x = k, y = tot_wc_ss)) +
    geom_point() + 
    geom_line()+
    labs(x = "Number of clusters",y = 'Total within-cluster sum of squares') + 
    theme_classic()

Normalized variables clustering visualizaiton of optimal K

# Run k-means for k = centers = 3
set.seed(253)
kclust_k3 <- kmeans(breastCa_Re_new_sub, centers = 3)

# Display the cluster assignments
kclust_k3$cluster
##   [1] 3 3 3 1 3 2 3 2 2 2 2 2 3 2 2 2 2 2 3 2 2 1 2 3 2 3 2 3 2 3 3 1 3 3 2 2 2
##  [38] 2 2 2 2 1 3 2 2 3 1 2 1 2 1 2 1 3 2 1 3 2 2 1 1 1 2 1 2 2 1 1 1 1 3 1 3 2
##  [75] 1 2 2 3 3 2 1 2 3 3 1 3 2 3 1 2 2 2 2 2 2 3 1 1 1 2 2 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 3 1 3 3 2 2 2 2 3 2 3 1 2 2 2 3 1 1 1 2 1 1 2 1 2 1 1 1 2
## [149] 2 2 2 1 1 1 2 1 3 2 1 1 1 3 3 1 3 2 1 2 3 2 1 2 2 1 1 1 1 2 1 1 3 3 2 1 2
## [186] 1 3 1 1 1 2 1 1 1 2 2 2 3 3 2 1 3 3 2 1 2 1 2 2 2 3 1 3 3 2 2 1 1 3 3 2 2
## [223] 1 2 2 2 1 2 1 2 3 1 1 3 1 2 3 3 2 3 2 1 1 2 3 1 2 2 1 1 3 1 3 3 3 2 3 2 2
## [260] 2 3 2 3 2 2 3 1 2 1 1 2 1 3 1 3 1 1 3 2 2 3 1 3 2 2 1 1 1 1 1 2 2 2 1 1 2
## [297] 1 1 2 1 3 1 3 1 1 1 2 1 2 2 1 2 1 1 1 1 1 3 1 1 1 3 2 3 1 1 2 1 2 2 2 2 1
## [334] 1 1 2 2 3 1 3 2 1 1 3 1 1 1 2 1 1 1 2 3 2 1 1 2 2 1 1 1 2 1 2 2 3 3 1 3 3
## [371] 2 2 3 3 2 2 1 2 2 1 1 1 1 1 2 2 1 2 1 3 1 1 2 3 1 2 2 2 1 1 3 1 2 2 1 1 2
## [408] 2 3 1 1 1 1 2 2 1 1 2 1 1 1 2 1 2 1 1 1 1 1 1 2 1 3 3 2 2 2 2 2 2 1 2 2 1
## [445] 3 1 3 2 2 3 1 3 1 2 1 2 1 2 2 1 2 3 2 1 2 2 2 1 3 1 1 1 2 1 1 2 2 2 1 2 1
## [482] 2 2 2 2 2 2 3 1 2 1 3 3 1 2 2 2 1 3 3 2 2 1 3 1 1 1 1 2 2 1 2 2 2 2 1 3 3
## [519] 2 2 1 3 1 2 1 1 2 1 2 1 1 1 2 3 1 3 2 1 1 1 1 2 2 2 2 2 1 1 1 1 1 1 1 1 2
## [556] 1 1 1 2 1 2 1 2 3 3 3 2 3 1
# Run k-means on the *scaled* data (all variables have SD = 1)
set.seed(253)
kclust_k3_scale <- kmeans(scale(breastCa_Re_new_sub), centers = 3)
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_3_scale = factor(kclust_k3_scale$cluster))

# Visualize the new cluster assignments
newClusterKPlot <- ggplot(
  breastCa_Re_new,
  aes(
    x = perimeter_mean,
    y = `concave points_worst`,
    color = kclust_3_scale,
    text = paste('diagnosis: ', diagnosis)
  )
) +
  geom_point() +
  theme_classic()

ggplotly(newClusterKPlot  , tooltip = c( "text"))

Perform clustering on more variabels with K=3

set.seed(253)
kclust_k3_allvars <- kmeans(scale(breastCa_Re_new_sub2), centers = 3)
#within clusters su of squares
kclust_k3_allvars
## K-means clustering with 3 clusters of sizes 370, 93, 106
## 
## Cluster means:
##   radius_mean texture_mean perimeter_mean   area_mean smoothness_mean
## 1 -0.47294644   -0.2447179    -0.49151681 -0.46834420      -0.3393479
## 2 -0.01575391    0.2342339     0.05989892 -0.08895702       0.9405564
## 3  1.66467260    0.6486969     1.66311907  1.71283356       0.3593111
##   compactness_mean concavity_mean concave points_mean fractal_dimension_mean
## 1       -0.5408815     -0.5826564          -0.5916723             -0.1707039
## 2        1.1632023      0.9455177           0.6854667              1.0983509
## 3        0.8674372      1.2042428           1.4638711             -0.3677942
##   radius_worst texture_worst perimeter_worst  area_worst smoothness_worst
## 1   -0.5121756    -0.2770056      -0.5286914 -0.49367809       -0.3671331
## 2    0.1171932     0.4474237       0.2045023  0.01879255        1.0989419
## 3    1.6849621     0.5743552       1.6660104  1.70672818        0.3173362
##   compactness_worst concavity_worst concave points_worst symmetry_worst
## 1        -0.5208214      -0.5605965           -0.6036665     -0.3364717
## 2         1.3364447       1.2237501            0.9683800      1.0287464
## 3         0.6454203       0.8831314            1.2575212      0.2718973
##   fractal_dimension_worst concave_points_mean
## 1             -0.37590256          -0.5916723
## 2              1.43420972           0.6854667
## 3              0.05379665           1.4638711
## 
## Clustering vector:
##   [1] 2 3 3 2 3 2 3 2 2 2 1 2 3 1 2 2 1 2 3 1 1 1 2 3 3 2 2 3 2 3 3 2 3 3 2 2 2
##  [38] 1 1 2 1 2 3 2 2 3 1 2 1 1 1 1 1 3 1 1 3 2 1 1 1 1 2 1 2 2 1 1 2 1 3 1 2 1
##  [75] 1 1 1 3 3 1 1 2 3 3 1 3 1 3 1 1 1 1 1 1 2 3 1 1 1 2 1 1 1 1 1 2 1 1 3 1 1
## [112] 1 2 1 1 1 1 2 2 1 1 3 3 1 1 1 1 3 2 3 1 2 2 1 3 1 1 1 2 1 1 1 1 1 1 1 2 1
## [149] 1 1 1 2 2 1 1 1 3 1 1 1 1 3 3 1 3 1 1 1 3 1 1 1 2 1 1 1 1 2 1 1 3 3 2 1 1
## [186] 1 3 1 1 1 2 1 1 2 2 1 2 1 3 2 1 3 3 2 1 1 1 1 2 1 3 1 3 1 2 2 1 1 3 3 1 1
## [223] 1 2 1 1 1 1 1 2 2 1 1 3 1 1 3 3 1 3 1 1 2 1 3 1 1 2 1 1 3 1 3 3 3 1 3 2 2
## [260] 2 3 1 3 1 3 3 1 1 1 1 1 1 3 1 1 1 1 1 1 1 3 1 3 2 1 1 1 1 1 1 1 1 1 1 1 1
## [297] 1 1 1 1 3 1 3 1 1 1 1 1 1 1 1 1 1 1 1 1 1 3 2 1 1 3 1 3 1 1 1 1 2 2 2 1 1
## [334] 1 1 3 1 3 1 3 1 1 1 3 1 1 1 1 1 1 1 2 3 2 1 1 1 1 1 1 1 1 1 1 1 3 3 1 3 3
## [371] 2 1 3 3 1 1 2 1 1 2 1 1 1 1 1 1 1 1 1 3 1 1 2 3 1 1 1 1 1 1 2 1 1 1 1 1 1
## [408] 1 3 1 1 1 1 1 1 1 1 2 1 1 1 2 1 1 1 1 1 1 1 1 2 1 3 3 1 2 1 1 1 1 1 3 1 1
## [445] 3 1 3 1 1 3 1 3 1 1 1 1 1 1 1 1 3 3 1 1 1 2 1 1 3 2 1 1 1 1 1 1 1 1 1 2 1
## [482] 1 1 1 1 2 1 3 1 1 1 1 3 1 1 1 2 1 3 3 1 2 1 3 2 2 1 1 1 2 1 1 2 1 1 1 3 3
## [519] 1 1 1 3 1 1 1 1 1 1 1 1 1 1 1 3 1 3 2 2 1 1 1 2 1 1 1 1 1 1 1 1 1 1 1 1 1
## [556] 1 1 1 1 1 1 1 2 3 3 3 1 3 1
## 
## Within cluster sum of squares by cluster:
## [1] 2779.416 1314.407 1403.885
##  (between_SS / total_SS =  51.6 %)
## 
## Available components:
## 
## [1] "cluster"      "centers"      "totss"        "withinss"     "tot.withinss"
## [6] "betweenss"    "size"         "iter"         "ifault"
breastCa_Re_new <- breastCa_Re_new %>%
    mutate(kclust_k3_allvars = factor(kclust_k3_allvars$cluster))


breastCa_Re_new %>%
  count(diagnosis,kclust_k3_allvars)

Interpreting the clusters with k=3

breastCa_Re_new %>%
    group_by(kclust_k3_allvars) %>%
    summarize(across(c(2:21), mean))
LS0tDQp0aXRsZTogImNsdXN0ZXIiDQphdXRob3I6ICJKZW5ueSBMaSwgTGl6IENhbywgS3Jpc3R5IE1hIg0KZGF0ZTogJzIwMjItMDQtMDcnDQpvdXRwdXQ6IA0KICBodG1sX2RvY3VtZW50Og0KICAgIHRvYzogVFJVRQ0KICAgIHRvY19mbG9hdDogVFJVRQ0KICAgIHRoZW1lOiBqb3VybmFsDQogICAgZGZfcHJpbnQ6IHBhZ2VkDQogICAgY29kZV9kb3dubG9hZDogdHJ1ZQ0KLS0tDQoNCmBgYHtyIHNldHVwLCBpbmNsdWRlPUZBTFNFfQ0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFLCBldmFsID0gVFJVRSwgd2FybmluZyA9IEZBTFNFLCBtZXNzYWdlID0gRkFMU0UsIHRpZHkgPSBUUlVFKQ0KYGBgDQoNCiMgR2VuZXJhbCBTZXQgVVANCg0KIyMgTGlicmFyeQ0KYGBge3IsIGxpYnJhcnl9DQpsaWJyYXJ5KGRwbHlyKQ0KbGlicmFyeShyZWFkcikNCmxpYnJhcnkoYnJvb20pDQpsaWJyYXJ5KGdncGxvdDIpDQpsaWJyYXJ5KHRpZHltb2RlbHMpIA0KbGlicmFyeShwcm9iYWJseSkNCmxpYnJhcnkodmlwKQ0KbGlicmFyeShwbG90bHkpDQpsaWJyYXJ5KENsdXN0ZXJSKQ0KbGlicmFyeShjbHVzdGVyKQ0KbGlicmFyeSh2aXApDQp0aWR5bW9kZWxzX3ByZWZlcigpDQp0aGVtZV9zZXQodGhlbWVfYncoKSkgICAgICAgDQpTeXMuc2V0bG9jYWxlKCJMQ19USU1FIiwgIkVuZ2xpc2giKQ0Kc2V0LnNlZWQoNzQpDQpgYGANCg0KIyMgUmVhZCBpbiBkYXRhDQoNCmBgYHtyLCByZWFkaW5nIGRhdGF9DQpicmVhc3RDYTwtcmVhZF9jc3YoZmlsZSA9ICJicmVhc3QtY2FuY2VyLmNzdiIpDQpgYGANCg0KIyBSZWdyZXNzaW9uDQoNCiMjIERhdGEgY2xlYW5pbmcNCmBgYHtyfQ0KYnJlYXN0Q2FfUmU8LWJyZWFzdENhICU+JSANCiAgZHJvcF9uYSgpICU+JSANCiAgc2VsZWN0KHJhZGl1c19tZWFuOmZyYWN0YWxfZGltZW5zaW9uX21lYW4pIA0KDQpicmVhc3RDYV9SZV9uZXc8LWJyZWFzdENhX1JlJT4lDQogIG11dGF0ZShjb25jYXZlX3BvaW50c19tZWFuPWBjb25jYXZlIHBvaW50c19tZWFuYCklPiUNCiAgc2VsZWN0KC04KQ0KYGBgDQoNCiMjIENyZWF0aW9uIG9mIGN2IGZvbGRzDQpgYGB7cn0NCmJyZWFzdENhX1JlX0NWPC12Zm9sZF9jdihicmVhc3RDYV9SZSwgdiA9IDEwKQ0KYGBgDQoNCiMjIE1vZGVsIHNwZWMNCmBgYHtyfQ0KI2xlYXN0IHNxdWFyZQ0KbG1fc3BlYyA8LQ0KICAgIGxpbmVhcl9yZWcoKSAlPiUgDQogICAgc2V0X2VuZ2luZShlbmdpbmUgPSAnbG0nKSAlPiUgDQogICAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQ0KDQojTEFTU08NCmxtX2xhc3NvX3NwZWMgPC0gDQogIGxpbmVhcl9yZWcoKSAlPiUNCiAgc2V0X2FyZ3MobWl4dHVyZSA9IDEsIHBlbmFsdHkgPSB0dW5lKCkpICU+JSAjIyBtaXh0dXJlID0gMSBpbmRpY2F0ZXMgTGFzc28NCiAgc2V0X2VuZ2luZShlbmdpbmUgPSAnZ2xtbmV0JykgJT4lICNub3RlIHdlIGFyZSB1c2luZyBhIGRpZmZlcmVudCBlbmdpbmUNCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKQ0KYGBgDQoNCiMjIFJlY2lwZXMgJiB3b3JrZmxvd3MNCmBgYHtyfQ0KI2xlYXN0IHNxdWFyZQ0KbGVhc3RfcmVjIDwtIHJlY2lwZShhcmVhX21lYW4gfiAuLCBkYXRhID0gYnJlYXN0Q2FfUmUpICU+JQ0KICAgIHN0ZXBfY29ycihhbGxfcHJlZGljdG9ycygpKSAlPiUgDQogICAgc3RlcF9uenYoYWxsX3ByZWRpY3RvcnMoKSkgJT4lICMgcmVtb3ZlcyB2YXJpYWJsZXMgd2l0aCB0aGUgc2FtZSB2YWx1ZQ0KICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lICMgaW1wb3J0YW50IHN0YW5kYXJkaXphdGlvbiBzdGVwIGZvciBMQVNTTw0KICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQ0KDQpsZWFzdF9sbV93ZiA8LSB3b3JrZmxvdygpICU+JQ0KICAgIGFkZF9yZWNpcGUobGVhc3RfcmVjKSAlPiUNCiAgICBhZGRfbW9kZWwobG1fc3BlYykNCiAgICANCiNMQVNTTw0KbGFzc29fd2Y8LSB3b3JrZmxvdygpICU+JSANCiAgYWRkX3JlY2lwZShsZWFzdF9yZWMpICU+JQ0KICBhZGRfbW9kZWwobG1fbGFzc29fc3BlYykgDQpgYGANCg0KIyMgRml0ICYgdHVuZSBtb2RlbHMNCmBgYHtyfQ0KI2xlYXN0IHNxdWFyZQ0KbGVhc3RfZml0IDwtIGZpdChsZWFzdF9sbV93ZiwgZGF0YSA9IGJyZWFzdENhX1JlKSANCg0KbGVhc3RfZml0ICU+JSB0aWR5KCkNCg0KI0xBU1NPDQojdHVuZQ0KcGVuYWx0eV9ncmlkIDwtIGdyaWRfcmVndWxhcigNCiAgcGVuYWx0eShyYW5nZSA9IGMoLTMsIDEpKSwgI2xvZzEwIHRyYW5zZm9ybWVkIA0KICBsZXZlbHMgPSAzMCkNCg0KdHVuZV9vdXRwdXQgPC0gdHVuZV9ncmlkKCAjIG5ldyBmdW5jdGlvbiBmb3IgdHVuaW5nIGh5cGVycGFyYW1ldGVycw0KICBsYXNzb193ZiwgIyB3b3JrZmxvdw0KICByZXNhbXBsZXMgPSBicmVhc3RDYV9SZV9DViwgIyBjdiBmb2xkcw0KICBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCBtYWUpLA0KICBncmlkID0gcGVuYWx0eV9ncmlkICMgcGVuYWx0eSBncmlkIGRlZmluZWQgYWJvdmUNCikNCg0KI2ZpdA0KYmVzdF9zZV9wZW5hbHR5IDwtIHNlbGVjdF9ieV9vbmVfc3RkX2Vycih0dW5lX291dHB1dCwgbWV0cmljID0gJ21hZScsIGRlc2MocGVuYWx0eSkpDQpmaW5hbF93Zl9zZSA8LSBmaW5hbGl6ZV93b3JrZmxvdyhsYXNzb193ZiwgYmVzdF9zZV9wZW5hbHR5KQ0KbGFzc29fZml0IDwtIGZpdChmaW5hbF93Zl9zZSAsIGRhdGEgPSBicmVhc3RDYV9SZSkNCmxhc3NvX2ZpdCAlPiUgdGlkeSgpDQoNCmBgYA0KDQojIyBDYWxjdWxhdGUgYW5kIGNvbGxlY3QgQ1YgbWV0cmljcw0KDQpgYGB7cn0NCiMgTGVhc3QgU3F1YXJlIG1vZGVsDQpsZWFzdF9maXRfY3YgPC0gZml0X3Jlc2FtcGxlcyhsZWFzdF9sbV93ZiwNCiAgcmVzYW1wbGVzID0gYnJlYXN0Q2FfUmVfQ1YsIA0KICBtZXRyaWNzID0gbWV0cmljX3NldChybXNlLCBtYWUpDQopDQoNCmxlYXN0X2ZpdF9jdiAlPiUgY29sbGVjdF9tZXRyaWNzKHN1bW1hcml6ZSA9IFRSVUUpDQpgYGANCmBgYHtyfQ0KIyBMQVNTTyBtb2RlbA0KdHVuZV9vdXRwdXQgJT4lIA0KICBjb2xsZWN0X21ldHJpY3MoKSAlPiUgDQogIGZpbHRlcihwZW5hbHR5ID09IChiZXN0X3NlX3BlbmFsdHkgDQogICAgICAgICAgICAgICAgICAgICAlPiUgcHVsbChwZW5hbHR5KSkpDQpgYGANCg0KIyMgUmVzaWR1YWwgUGxvdHMgDQpgYGB7cn0NCiNsZWFzdCBzcXVhcmUNCmxlYXN0X2ZpdF9vdXRwdXQgPC0gbGVhc3RfZml0ICU+JQ0KICBwcmVkaWN0KG5ld19kYXRhID0gYnJlYXN0Q2FfUmUpICU+JQ0KICBiaW5kX2NvbHMoYnJlYXN0Q2FfUmUpICU+JQ0KICBtdXRhdGUocmVzaWQgPSBhcmVhX21lYW4gLSAucHJlZCkNCg0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gLnByZWQsIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgbGFicyh4ID0gIkZpdHRlZCB2YWx1ZXMiLCB5ID0gIlJlc2lkdWFscyIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gY29uY2F2aXR5X21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJDb25jYXZpdHkgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxlYXN0X2ZpdF9vdXRwdXQsIGFlcyh4ID0gY29tcGFjdG5lc3NfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkNvbXBhY3RuZXNzIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsZWFzdF9maXRfb3V0cHV0LCBhZXMoeCA9IGZyYWN0YWxfZGltZW5zaW9uX21lYW4sIHkgPSByZXNpZCkpICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgZ2VvbV9zbW9vdGgoKSArDQogIGxhYnMoeCA9ICJGcmFjdGFsIGRpbWVuc2lvbiBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGVhc3RfZml0X291dHB1dCwgYWVzKHggPSByYWRpdXNfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIlJhZGl1cyBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpgYGB7cn0NCiNMQVNTTw0KbGFzc29fZml0X291dHB1dCA8LSBsYXNzb19maXQgJT4lDQogIHByZWRpY3QobmV3X2RhdGEgPSBicmVhc3RDYV9SZSkgJT4lDQogIGJpbmRfY29scyhicmVhc3RDYV9SZSkgJT4lDQogIG11dGF0ZShyZXNpZCA9IGFyZWFfbWVhbiAtIC5wcmVkKQ0KDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSAucHJlZCwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICBsYWJzKHggPSAiRml0dGVkIHZhbHVlcyIsIHkgPSAiUmVzaWR1YWxzIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSBjb25jYXZpdHlfbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkNvbmNhdml0eSBtZWFuIiwgeSA9ICJSZXNpZHVhbCIpICsNCiAgZ2VvbV9obGluZSh5aW50ZXJjZXB0ID0gMCwgY29sb3IgPSAicmVkIikgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KIyBSZXNpZHVhbHMgdnMuIHByZWRpY3RvcnMgKHgncykgDQpnZ3Bsb3QobGFzc29fZml0X291dHB1dCwgYWVzKHggPSBjb21wYWN0bmVzc19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiQ29tcGFjdG5lc3MgbWVhbiIsIHkgPSAiUmVzaWR1YWwiKSArDQogIGdlb21faGxpbmUoeWludGVyY2VwdCA9IDAsIGNvbG9yID0gInJlZCIpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCiMgUmVzaWR1YWxzIHZzLiBwcmVkaWN0b3JzICh4J3MpIA0KZ2dwbG90KGxhc3NvX2ZpdF9vdXRwdXQsIGFlcyh4ID0gZnJhY3RhbF9kaW1lbnNpb25fbWVhbiwgeSA9IHJlc2lkKSkgKw0KICBnZW9tX3BvaW50KCkgKw0KICBnZW9tX3Ntb290aCgpICsNCiAgbGFicyh4ID0gIkZyYWN0YWwgZGltZW5zaW9uIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KDQojIFJlc2lkdWFscyB2cy4gcHJlZGljdG9ycyAoeCdzKSANCmdncGxvdChsYXNzb19maXRfb3V0cHV0LCBhZXMoeCA9IHJhZGl1c19tZWFuLCB5ID0gcmVzaWQpKSArDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fc21vb3RoKCkgKw0KICBsYWJzKHggPSAiUmFkaXVzIG1lYW4iLCB5ID0gIlJlc2lkdWFsIikgKw0KICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQgPSAwLCBjb2xvciA9ICJyZWQiKSArDQogIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCiMjIEFjY291bnRpbmcgZm9yIG5vbmxpbmVhcml0eSANCg0KYGBge3J9DQpzZXQuc2VlZCgxMjMpDQoNCmdhbV9zcGVjIDwtIA0KICBnZW5fYWRkaXRpdmVfbW9kKCkgJT4lDQogIHNldF9lbmdpbmUoZW5naW5lID0gJ21nY3YnKSAlPiUNCiAgc2V0X21vZGUoJ3JlZ3Jlc3Npb24nKSANCg0KZ2FtX21vZCA8LSBmaXQoZ2FtX3NwZWMsDQogICAgYXJlYV9tZWFuIH4gcyhyYWRpdXNfbWVhbikrdGV4dHVyZV9tZWFuK3MocGVyaW1ldGVyX21lYW4pK3Ntb290aG5lc3NfbWVhbitjb21wYWN0bmVzc19tZWFuK2NvbmNhdmVfcG9pbnRzX21lYW4rY29uY2F2aXR5X21lYW4rc3ltbWV0cnlfbWVhbitmcmFjdGFsX2RpbWVuc2lvbl9tZWFuLA0KICAgIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcNCikNCg0KcGFyKG1mcm93PWMoMiwyKSkNCmdhbV9tb2QgJT4lIHBsdWNrKCdmaXQnKSAlPiUgbWdjdjo6Z2FtLmNoZWNrKCkgDQpnYW1fbW9kICU+JSBwbHVjaygnZml0JykgJT4lIHN1bW1hcnkoKQ0KDQpuc19yZWMgPC0gbGVhc3RfcmVjICU+JQ0KICBzdGVwX25zKHgsIGRlZ19mcmVlID0gOSkNCm5zOV93ZiA8LSB3b3JrZmxvdygpICAlPiUNCiAgYWRkX3JlY2lwZShuc19yZWMpICU+JQ0KICBhZGRfbW9kZWwobG1fc3BlYykNCg0KaGlzdChicmVhc3RDYV9SZV9uZXckYXJlYV9tZWFuKQ0KDQpnYW1fbW9kICU+JSBwbHVjaygnZml0JykgJT4lIHBsb3QoKQ0KYGBgDQoNCiMjIEV2YWx1YXRpb24gb2YgdGhlIEdBTSBtb2RlbCBvbiBUZXN0IERhdGEgDQoNCmBgYHtyfQ0KZ2FtX3Rlc3Rfb3V0cHV0IDwtIGdhbV9tb2QgJT4lDQogIHByZWRpY3QobmV3X2RhdGE9YnJlYXN0Q2FfUmVfbmV3KSAlPiUNCiAgYmluZF9jb2xzKGJyZWFzdENhX1JlX25ldyAlPiUgc2VsZWN0KGFyZWFfbWVhbikpDQoNCmdhbV90ZXN0X291dHB1dCAlPiUNCiAgcm1zZSh0cnV0aD1hcmVhX21lYW4sIGVzdGltYXRlPS5wcmVkKSANCg0KZ2FtX3Rlc3Rfb3V0cHV0ICU+JQ0KICBtYWUodHJ1dGg9YXJlYV9tZWFuLCBlc3RpbWF0ZT0ucHJlZCkgDQpgYGANCg0KIyBDbGFzc2lmaWNhdGlvbg0KDQojIyBEYXRhIENsZWFuaW5nDQpgYGB7cn0NCmJyZWFzdENhX1JlPC1icmVhc3RDYSAlPiUgDQogIGRyb3BfbmEoKSAlPiUgDQogIHNlbGVjdCgtYygxMzoyMikpICU+JSANCiAgc2VsZWN0KC0xKQ0KDQpicmVhc3RDYV9SZV9uZXc8LWJyZWFzdENhX1JlJT4lDQogIG11dGF0ZShjb25jYXZlX3BvaW50c19tZWFuPWBjb25jYXZlIHBvaW50c19tZWFuYCklPiUNCiAgc2VsZWN0KC0xMCkNCmBgYA0KDQojIyBMQVNTTyBhbmQgTG9naXN0aWMgUmVncmVzc2lvbg0KDQojIyMgSW1wbGV0ZSBMYXNzbyBMb2dpc3RpYyBSZWdyZXNzaW9uIGluIHRpZHltb2RlbHMNCmBgYHtyfQ0KDQojIE1ha2Ugc3VyZSB5b3Ugc2V0IHJlZmVyZW5jZSBsZXZlbCAodG8gdGhlIG91dGNvbWUgeW91IGFyZSBOT1QgaW50ZXJlc3RlZCBpbikNCmJyZWFzdENhX1JlX25ldzIgPC0gYnJlYXN0Q2FfUmVfbmV3JT4lDQogIG11dGF0ZShkaWFnbm9zaXMgPSByZWxldmVsKGZhY3RvcihkaWFnbm9zaXMgKSwgcmVmPSdCJykpICNzZXQgcmVmZXJlbmNlIGxldmVsDQoNCmRhdGFfY3YxMCA8LSB2Zm9sZF9jdihicmVhc3RDYV9SZV9uZXcyLCB2ID0gMTApDQoNCg0KIyBMb2dpc3RpYyBMQVNTTyBSZWdyZXNzaW9uIE1vZGVsIFNwZWMNCmxvZ2lzdGljX2xhc3NvX3NwZWNfdHVuZSA8LSBsb2dpc3RpY19yZWcoKSAlPiUNCiAgICBzZXRfZW5naW5lKCdnbG1uZXQnKSAlPiUNCiAgICBzZXRfYXJncyhtaXh0dXJlID0gMSwgcGVuYWx0eSA9IHR1bmUoKSkgJT4lDQogICAgc2V0X21vZGUoJ2NsYXNzaWZpY2F0aW9uJykNCg0KIyBSZWNpcGUNCmxvZ2lzdGljX3JlYyA8LSByZWNpcGUoZGlhZ25vc2lzIH4gLiwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpICU+JQ0KICAgIHN0ZXBfbm9ybWFsaXplKGFsbF9udW1lcmljX3ByZWRpY3RvcnMoKSkgJT4lIA0KICAgIHN0ZXBfZHVtbXkoYWxsX25vbWluYWxfcHJlZGljdG9ycygpKQ0KDQojIFdvcmtmbG93IChSZWNpcGUgKyBNb2RlbCkNCmxvZ19sYXNzb193ZiA8LSB3b3JrZmxvdygpICU+JSANCiAgICBhZGRfcmVjaXBlKGxvZ2lzdGljX3JlYykgJT4lDQogICAgYWRkX21vZGVsKGxvZ2lzdGljX2xhc3NvX3NwZWNfdHVuZSkgDQoNCiMgVHVuZSBNb2RlbCAodHJ5aW5nIGEgdmFyaWV0eSBvZiB2YWx1ZXMgb2YgTGFtYmRhIHBlbmFsdHkpDQpwZW5hbHR5X2dyaWQgPC0gZ3JpZF9yZWd1bGFyKA0KICBwZW5hbHR5KHJhbmdlID0gYygtNSwgMSkpLCAjbG9nMTAgdHJhbnNmb3JtZWQgIChrZXB0IG1vdmluZyBtaW4gZG93biBmcm9tIDApDQogIGxldmVscyA9IDEwMCkNCg0KdHVuZV9vdXRwdXQgPC0gdHVuZV9ncmlkKCANCiAgbG9nX2xhc3NvX3dmLCAjIHdvcmtmbG93DQogIHJlc2FtcGxlcyA9IGRhdGFfY3YxMCwgIyBjdiBmb2xkcw0KICBtZXRyaWNzID0gbWV0cmljX3NldChyb2NfYXVjLGFjY3VyYWN5KSwNCiAgY29udHJvbCA9IGNvbnRyb2xfcmVzYW1wbGVzKHNhdmVfcHJlZCA9IFRSVUUsIGV2ZW50X2xldmVsID0gJ3NlY29uZCcpLA0KICBncmlkID0gcGVuYWx0eV9ncmlkICMgcGVuYWx0eSBncmlkIGRlZmluZWQgYWJvdmUNCikNCg0KIyBWaXN1YWxpemUgTW9kZWwgRXZhbHVhdGlvbiBNZXRyaWNzIGZyb20gVHVuaW5nDQphdXRvcGxvdCh0dW5lX291dHB1dCkgKyB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyMgSW5zcGVjdGluZyB0aGUgTW9kZWwNCmBgYHtyfQ0KYmVzdF9zZV9wZW5hbHR5IDwtIHNlbGVjdF9ieV9vbmVfc3RkX2Vycih0dW5lX291dHB1dCwgbWV0cmljID0gJ3JvY19hdWMnLCBkZXNjKHBlbmFsdHkpKSAjIGNob29zZSBwZW5hbHR5IHZhbHVlIGJhc2VkIG9uIHRoZSBsYXJnZXN0IHBlbmFsdHkgd2l0aGluIDEgc2Ugb2YgdGhlIGhpZ2hlc3QgQ1Ygcm9jX2F1Yw0KZmluYWxfZml0X3NlIDwtIGZpbmFsaXplX3dvcmtmbG93KGxvZ19sYXNzb193ZiwgYmVzdF9zZV9wZW5hbHR5KSAlPiUgIyBpbmNvcnBvcmF0ZXMgcGVuYWx0eSB2YWx1ZSB0byB3b3JrZmxvdyANCiAgICBmaXQoZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQoNCmZpbmFsX2ZpdF9zZSAlPiUgdGlkeSgpDQoNCmZpbmFsX2ZpdF9zZSAlPiUgdGlkeSgpICU+JQ0KICBmaWx0ZXIoZXN0aW1hdGUgPT0gMCkNCg0KI3ZhcmlhYmxlIGltcG9ydGFuY2UNCmdsbW5ldF9vdXRwdXQgPC0gZmluYWxfZml0X3NlICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKQ0KICAgIA0KIyBDcmVhdGUgYSBib29sZWFuIG1hdHJpeCAocHJlZGljdG9ycyB4IGxhbWJkYXMpIG9mIHZhcmlhYmxlIGV4Y2x1c2lvbg0KYm9vbF9wcmVkaWN0b3JfZXhjbHVkZSA8LSBnbG1uZXRfb3V0cHV0JGJldGE9PTANCg0KIyBMb29wIG92ZXIgZWFjaCB2YXJpYWJsZQ0KdmFyX2ltcCA8LSBzYXBwbHkoc2VxX2xlbihucm93KGJvb2xfcHJlZGljdG9yX2V4Y2x1ZGUpKSwgZnVuY3Rpb24ocm93KSB7DQogICAgIyBFeHRyYWN0IGNvZWZmaWNpZW50IHBhdGggKHNvcnRlZCBmcm9tIGhpZ2hlc3QgdG8gbG93ZXN0IGxhbWJkYSkNCiAgICB0aGlzX2NvZWZmX3BhdGggPC0gYm9vbF9wcmVkaWN0b3JfZXhjbHVkZVtyb3csXQ0KICAgICMgQ29tcHV0ZSBhbmQgcmV0dXJuIHRoZSAjIG9mIGxhbWJkYXMgdW50aWwgdGhpcyB2YXJpYWJsZSBpcyBvdXQgZm9yZXZlcg0KICAgIG5jb2woYm9vbF9wcmVkaWN0b3JfZXhjbHVkZSkgLSB3aGljaC5taW4odGhpc19jb2VmZl9wYXRoKSArIDENCn0pDQoNCiMgQ3JlYXRlIGEgZGF0YXNldCBvZiB0aGlzIGluZm9ybWF0aW9uIGFuZCBzb3J0DQp2YXJfaW1wX2RhdGEgPC0gdGliYmxlKA0KICAgIHZhcl9uYW1lID0gcm93bmFtZXMoYm9vbF9wcmVkaWN0b3JfZXhjbHVkZSksDQogICAgdmFyX2ltcCA9IHZhcl9pbXANCikNCnZhcl9pbXBfZGF0YSAlPiUgYXJyYW5nZShkZXNjKHZhcl9pbXApKQ0KYGBgDQoNCiMjIyBFdmFsdWF0aW9uIE1ldHJpY3MNCmBgYHtyfQ0KIyBDViByZXN1bHRzIGZvciAiYmVzdCBsYW1iZGEiDQp0dW5lX291dHB1dCAlPiUNCiAgICBjb2xsZWN0X21ldHJpY3MoKSAlPiUNCiAgICBmaWx0ZXIocGVuYWx0eSA9PSBiZXN0X3NlX3BlbmFsdHkgJT4lIHB1bGwocGVuYWx0eSkpDQoNCiMgQ291bnQgdXAgbnVtYmVyIG9mIEIgYW5kIE0gaW4gdGhlIHRyYWluaW5nIGRhdGENCmJyZWFzdENhX1JlX25ldzIgJT4lDQogICAgY291bnQoZGlhZ25vc2lzKSAjIE5hbWUgb2YgdGhlIG91dGNvbWUgdmFyaWFibGUgZ29lcyBpbnNpZGUgY291bnQoKQ0KDQojQ29tcHV0ZSB0aGUgTklSDQpOSVI8LSAzNTcvKDM1NysyMTIpDQpOSVINCmBgYA0KDQojIyMgVGhyZXNob2xkDQpgYGB7cn0NCiMgU29mdCBQcmVkaWN0aW9ucyBvbiBUcmFpbmluZyBEYXRhDQpmaW5hbF9vdXRwdXQgPC0NCiAgZmluYWxfZml0X3NlICU+JSBwcmVkaWN0KG5ld19kYXRhID0gYnJlYXN0Q2FfUmVfbmV3MiwgdHlwZSA9ICdwcm9iJykgJT4lICAgICBiaW5kX2NvbHMoYnJlYXN0Q2FfUmVfbmV3MikNCg0KDQoNCmZpbmFsX291dHB1dCAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZGlhZ25vc2lzLCB5ID0gLnByZWRfTSkpICsNCiAgZ2VvbV9ib3hwbG90KCkNCg0KIyBVc2Ugc29mdCBwcmVkaWN0aW9ucw0KZmluYWxfb3V0cHV0ICU+JQ0KICAgIHJvY19jdXJ2ZShkaWFnbm9zaXMsLnByZWRfTSxldmVudF9sZXZlbCA9ICdzZWNvbmQnKSAlPiUNCiAgICBhdXRvcGxvdCgpDQoNCiMgdGhyZXNob2xkcyBpbiB0ZXJtcyBvZiByZWZlcmVuY2UgbGV2ZWwNCnRocmVzaG9sZF9vdXRwdXQgPC0gZmluYWxfb3V0cHV0ICU+JQ0KICAgIHRocmVzaG9sZF9wZXJmKHRydXRoID0gZGlhZ25vc2lzLCBlc3RpbWF0ZSA9IC5wcmVkX0IsIHRocmVzaG9sZHMgPSBzZXEoMCwxLGJ5PS4wMSkpIA0KDQojIEotaW5kZXggdi4gdGhyZXNob2xkIGZvciBub3QgTQ0KdGhyZXNob2xkX291dHB1dCAlPiUNCiAgICBmaWx0ZXIoLm1ldHJpYyA9PSAnal9pbmRleCcpICU+JQ0KICAgIGdncGxvdChhZXMoeCA9IC50aHJlc2hvbGQsIHkgPSAuZXN0aW1hdGUpKSArDQogICAgZ2VvbV9saW5lKCkgKw0KICAgIGxhYnMoeSA9ICdKLWluZGV4JywgeCA9ICd0aHJlc2hvbGQnKSArDQogICAgdGhlbWVfY2xhc3NpYygpDQoNCnRocmVzaG9sZF9vdXRwdXQgJT4lDQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2pfaW5kZXgnKSAlPiUNCiAgICBhcnJhbmdlKGRlc2MoLmVzdGltYXRlKSkNCg0KIyBEaXN0YW5jZSB2LiB0aHJlc2hvbGQgZm9yIG5vdCBNDQoNCnRocmVzaG9sZF9vdXRwdXQgJT4lDQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2Rpc3RhbmNlJykgJT4lDQogICAgZ2dwbG90KGFlcyh4ID0gLnRocmVzaG9sZCwgeSA9IC5lc3RpbWF0ZSkpICsNCiAgICBnZW9tX2xpbmUoKSArDQogICAgbGFicyh5ID0gJ0Rpc3RhbmNlJywgeCA9ICd0aHJlc2hvbGQnKSArDQogICAgdGhlbWVfY2xhc3NpYygpDQoNCnRocmVzaG9sZF9vdXRwdXQgJT4lDQogICAgZmlsdGVyKC5tZXRyaWMgPT0gJ2Rpc3RhbmNlJykgJT4lDQogICAgYXJyYW5nZSguZXN0aW1hdGUpDQoNCmxvZ19tZXRyaWNzIDwtIG1ldHJpY19zZXQoYWNjdXJhY3ksc2Vucyx5YXJkc3RpY2s6OnNwZWMpDQoNCmZpbmFsX291dHB1dCAlPiUNCiAgICBtdXRhdGUoLnByZWRfY2xhc3MgPSBtYWtlX3R3b19jbGFzc19wcmVkKC5wcmVkX0IsIGxldmVscyhkaWFnbm9zaXMpLCB0aHJlc2hvbGQgPSAuNjQpKSAlPiUNCiAgICBsb2dfbWV0cmljcyh0cnV0aCA9IGRpYWdub3NpcywgZXN0aW1hdGUgPSAucHJlZF9jbGFzcywgZXZlbnRfbGV2ZWwgPSAnc2Vjb25kJykNCmBgYA0KDQojIyBSYW5kb20gRm9yZXN0DQoNCiMjIyBCdWlsZGluZyBSYW5kb20gRm9yZXN0DQpgYGB7cn0NCiMgTW9kZWwgU3BlY2lmaWNhdGlvbg0KcmZfc3BlYyA8LSByYW5kX2ZvcmVzdCgpICU+JQ0KICBzZXRfZW5naW5lKGVuZ2luZSA9ICdyYW5nZXInKSAlPiUgDQogIHNldF9hcmdzKG10cnkgPSBOVUxMLCAjIHNpemUgb2YgcmFuZG9tIHN1YnNldCBvZiB2YXJpYWJsZXM7IGRlZmF1bHQgaXMgZmxvb3Ioc3FydChuY29sKHgpKSkNCiAgICAgICAgICAgdHJlZXMgPSAxMDAwLCAjIE51bWJlciBvZiB0cmVlcw0KICAgICAgICAgICBtaW5fbiA9IDIsDQogICAgICAgICAgIHByb2JhYmlsaXR5ID0gRkFMU0UsICMgRkFMU0U6IGhhcmQgcHJlZGljdGlvbnMNCiAgICAgICAgICAgaW1wb3J0YW5jZSA9ICdpbXB1cml0eScpICU+JSANCiAgc2V0X21vZGUoJ2NsYXNzaWZpY2F0aW9uJykgIyBjaGFuZ2UgdGhpcyBmb3IgcmVncmVzc2lvbiB0cmVlDQoNCiMgUmVjaXBlDQpkYXRhX3JlYyA8LSByZWNpcGUoZGlhZ25vc2lzIH4gLiwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQoNCiMgV29ya2Zsb3dzDQpkYXRhX3dmX210cnkyIDwtIHdvcmtmbG93KCkgJT4lDQogIGFkZF9tb2RlbChyZl9zcGVjICU+JSBzZXRfYXJncyhtdHJ5ID0gMikpICU+JQ0KICBhZGRfcmVjaXBlKGRhdGFfcmVjKQ0KDQojIENyZWF0ZSB3b3JrZmxvd3MgZm9yIG10cnkgPSA0ICwgMTAsIGFuZCAyMA0KZGF0YV93Zl9tdHJ5NCA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfbW9kZWwocmZfc3BlYyAlPiUgc2V0X2FyZ3MobXRyeSA9IDQpKSAlPiUNCiAgYWRkX3JlY2lwZShkYXRhX3JlYykNCg0KZGF0YV93Zl9tdHJ5MTAgPC0gd29ya2Zsb3coKSAlPiUNCiAgYWRkX21vZGVsKHJmX3NwZWMgJT4lIHNldF9hcmdzKG10cnkgPSAxMCkpICU+JQ0KICBhZGRfcmVjaXBlKGRhdGFfcmVjKQ0KDQpkYXRhX3dmX210cnkyMCA8LSB3b3JrZmxvdygpICU+JQ0KICBhZGRfbW9kZWwocmZfc3BlYyAlPiUgc2V0X2FyZ3MobXRyeSA9IDIwKSkgJT4lDQogIGFkZF9yZWNpcGUoZGF0YV9yZWMpDQpgYGANCg0KYGBge3J9DQojIEZpdCBNb2RlbHMNCg0Kc2V0LnNlZWQoMTIzKSAjIG1ha2Ugc3VyZSB0byBydW4gdGhpcyBiZWZvcmUgZWFjaCBmaXQgc28gdGhhdCB5b3UgaGF2ZSB0aGUgc2FtZSAxMDAwIHRyZWVzDQpkYXRhX2ZpdF9tdHJ5MiA8LSBmaXQoZGF0YV93Zl9tdHJ5MiwgZGF0YSA9IGJyZWFzdENhX1JlX25ldzIpDQoNCnNldC5zZWVkKDEyMykNCmRhdGFfZml0X210cnk0IDwtIGZpdChkYXRhX3dmX210cnk0LCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCg0Kc2V0LnNlZWQoMTIzKSANCmRhdGFfZml0X210cnkxMCA8LSBmaXQoZGF0YV93Zl9tdHJ5MTAsIGRhdGEgPSBicmVhc3RDYV9SZV9uZXcyKQ0KDQpzZXQuc2VlZCgxMjMpDQpkYXRhX2ZpdF9tdHJ5MjAgPC0gZml0KGRhdGFfd2ZfbXRyeTIwLCBkYXRhID0gYnJlYXN0Q2FfUmVfbmV3MikNCmBgYA0KDQpgYGB7cn0NCiMgQ3VzdG9tIEZ1bmN0aW9uIHRvIGdldCBPT0IgcHJlZGljdGlvbnMsIHRydWUgb2JzZXJ2ZWQgb3V0Y29tZXMgYW5kIGFkZCBhIG1vZGVsIGxhYmVsDQpyZl9PT0Jfb3V0cHV0IDwtIGZ1bmN0aW9uKGZpdF9tb2RlbCwgbW9kZWxfbGFiZWwsIHRydXRoKXsNCiAgICB0aWJibGUoDQogICAgICAgICAgLnByZWRfZGlhZ25vc2lzID0gZml0X21vZGVsICU+JSBleHRyYWN0X2ZpdF9lbmdpbmUoKSAlPiUgcGx1Y2soJ3ByZWRpY3Rpb25zJyksICNPT0IgcHJlZGljdGlvbnMNCiAgICAgICAgICBkaWFnbm9zaXMgPSB0cnV0aCwNCiAgICAgICAgICBtb2RlbCA9IG1vZGVsX2xhYmVsDQogICAgICApDQp9DQoNCiNjaGVjayBvdXQgdGhlIGZ1bmN0aW9uIG91dHB1dA0KcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MiwnbXRyeTInLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpDQpgYGANCg0KYGBge3J9DQojIEV2YWx1YXRlIE9PQiBNZXRyaWNzDQoNCmRhdGFfcmZfT09CX291dHB1dCA8LSBiaW5kX3Jvd3MoDQogICAgcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MiwnbXRyeTInLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpLA0KICAgIHJmX09PQl9vdXRwdXQoZGF0YV9maXRfbXRyeTQsJ210cnk0JywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKSwNCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnkxMCwnbXRyeTEwJywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKSwNCiAgICByZl9PT0Jfb3V0cHV0KGRhdGFfZml0X210cnkyMCwnbXRyeTIwJywgYnJlYXN0Q2FfUmVfbmV3MiAlPiUgcHVsbChkaWFnbm9zaXMpKQ0KKQ0KDQoNCmRhdGFfcmZfT09CX291dHB1dCAlPiUgDQogICAgZ3JvdXBfYnkobW9kZWwpICU+JQ0KICAgIGFjY3VyYWN5KHRydXRoID0gZGlhZ25vc2lzLCBlc3RpbWF0ZSA9IC5wcmVkX2RpYWdub3NpcykNCmBgYA0KDQojIyMgUHJlbGltaW5hcnkgaW50ZXJwcmV0YXRpb24NCmBgYHtyfQ0KZGF0YV9yZl9PT0Jfb3V0cHV0ICU+JSANCiAgICBncm91cF9ieShtb2RlbCkgJT4lDQogICAgYWNjdXJhY3kodHJ1dGggPSBkaWFnbm9zaXMsIGVzdGltYXRlID0ucHJlZF9kaWFnbm9zaXMpICU+JQ0KICBtdXRhdGUobXRyeSA9IGFzLm51bWVyaWMoc3RyaW5ncjo6c3RyX3JlcGxhY2UobW9kZWwsJ210cnknLCcnKSkpICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSBtdHJ5LCB5ID0gLmVzdGltYXRlICkpICsgDQogIGdlb21fcG9pbnQoKSArDQogIGdlb21fbGluZSgpICsNCiAgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KIyMjIEV2YWx1YXRpbmcgdGhlIGZvcmVzdA0KYGBge3J9DQpkYXRhX2ZpdF9tdHJ5Mg0KYGBgDQoNCmBgYHtyfQ0KcmZfT09CX291dHB1dChkYXRhX2ZpdF9tdHJ5MiwnbXRyeTInLCBicmVhc3RDYV9SZV9uZXcyICU+JSBwdWxsKGRpYWdub3NpcykpICU+JQ0KICAgIGNvbmZfbWF0KHRydXRoID0gZGlhZ25vc2lzLCBlc3RpbWF0ZT0gLnByZWRfZGlhZ25vc2lzKQ0KDQpgYGANCg0KIyMjIFZhcmlhYmxlIGltcG9ydGFuY2UgbWVhc3VyZXMNCmBgYHtyfQ0KZGF0YV9maXRfbXRyeTIgJT4lIA0KICAgIGV4dHJhY3RfZml0X2VuZ2luZSgpICU+JSANCiAgICB2aXAobnVtX2ZlYXR1cmVzID0gMzApICsgdGhlbWVfY2xhc3NpYygpDQpgYGANCg0KYGBge3J9DQpnZ3Bsb3QoYnJlYXN0Q2FfUmVfbmV3MiwgYWVzKHggPSBkaWFnbm9zaXMsIHkgPSBhcmVhX3dvcnN0KSkgKw0KICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQpgYGB7cn0NCmdncGxvdChicmVhc3RDYV9SZV9uZXcyLCBhZXMoeCA9IGRpYWdub3NpcywgeSA9IGZyYWN0YWxfZGltZW5zaW9uX21lYW4pKSArDQogICAgZ2VvbV92aW9saW4oKSArIHRoZW1lX2NsYXNzaWMoKQ0KYGBgDQoNCmBgYHtyfQ0KI2ludGVybWVkaWF0ZSBpbXBvcnRhbnQNCmdncGxvdChicmVhc3RDYV9SZV9uZXcyLCBhZXMoeCA9IGRpYWdub3NpcywgeSA9IHBlcmltZXRlcl9tZWFuKSkgKw0KICAgIGdlb21fdmlvbGluKCkgKyB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIENsdXN0ZXJpbmcNCg0KIyMgRGF0YSBDbGVhbmluZw0KYGBge3J9DQpicmVhc3RDYV9SZTwtYnJlYXN0Q2EgJT4lIA0KICBkcm9wX25hKCkgJT4lIA0KICBzZWxlY3QoLWMoMTM6MjIpKSAlPiUgDQogIHNlbGVjdCgtMSkNCg0KYnJlYXN0Q2FfUmVfbmV3PC1icmVhc3RDYV9SZSU+JQ0KICBtdXRhdGUoY29uY2F2ZV9wb2ludHNfbWVhbj1gY29uY2F2ZSBwb2ludHNfbWVhbmApJT4lDQogIHNlbGVjdCgtMTApIA0KYGBgDQoNCmBgYHtyfQ0KZ2dwbG90KGJyZWFzdENhX1JlX25ldywgYWVzKHggPSBwZXJpbWV0ZXJfbWVhbiwgeSA9IGBjb25jYXZlIHBvaW50c193b3JzdGApKSArDQogICAgZ2VvbV9wb2ludCgpICsNCiAgICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyBLLW1lYW5zIGNsdXN0ZXJpbmcgb24gcGVyaW1ldGVyX21lYW4gYW5kIGNvbmNhdmUgcG9pbnRzX3dvcnN0DQpgYGB7cn0NCiMgU2VsZWN0IGp1c3QgdGhlIHBlcmltZXRlcl9tZWFuIGFuZCBjb25jYXZlIHBvaW50c193b3JzdCB2YXJpYWJsZXMNCmJyZWFzdENhX1JlX25ld19zdWIgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIHNlbGVjdChwZXJpbWV0ZXJfbWVhbiwgYGNvbmNhdmUgcG9pbnRzX3dvcnN0YCkNCg0KIyBSdW4gay1tZWFucyBmb3IgayA9IGNlbnRlcnMgPSAyDQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazIgPC0ga21lYW5zKGJyZWFzdENhX1JlX25ld19zdWIsIGNlbnRlcnMgPSAyKQ0KDQojIERpc3BsYXkgdGhlIGNsdXN0ZXIgYXNzaWdubWVudHMNCmtjbHVzdF9rMiRjbHVzdGVyDQpgYGANCg0KYGBge3J9DQojIEFkZCBhIHZhcmlhYmxlIChrY2x1c3RfazIpIHRvIHRoZSBvcmlnaW5hbCBkYXRhc2V0IA0KIyBjb250YWluaW5nIHRoZSBjbHVzdGVyIGFzc2lnbm1lbnRzDQpicmVhc3RDYV9SZV9uZXcgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIG11dGF0ZShrY2x1c3RfMiA9IGZhY3RvcihrY2x1c3RfazIkY2x1c3RlcikpDQpgYGANCg0KYGBge3J9DQojIFZpc3VhbGl6ZSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cyBvbiB0aGUgb3JpZ2luYWwgc2NhdHRlcnBsb3QNCm9yaWdpbmFsQ2x1c3RlclBsb3QgPC0gZ2dwbG90KA0KICBicmVhc3RDYV9SZV9uZXcsDQogIGFlcygNCiAgICB4ID0gcGVyaW1ldGVyX21lYW4sDQogICAgeSA9IGBjb25jYXZlIHBvaW50c193b3JzdGAsDQogICAgY29sb3IgPSBrY2x1c3RfMiwNCiAgICB0ZXh0ID0gcGFzdGUoJ2RpYWdub3NpczogJywgZGlhZ25vc2lzKQ0KICApDQopICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmdncGxvdGx5KG9yaWdpbmFsQ2x1c3RlclBsb3QgICwgdG9vbHRpcCA9IGMoICJ0ZXh0IikpDQpgYGANCg0KIyMjIEFkZHJlc3NpbmcgdmFyaWFibGUgc2NhbGUNCg0KYGBge3J9DQojIFJ1biBrLW1lYW5zIG9uIHRoZSAqc2NhbGVkKiBkYXRhIChhbGwgdmFyaWFibGVzIGhhdmUgU0QgPSAxKQ0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2syX3NjYWxlIDwtIGttZWFucyhzY2FsZShicmVhc3RDYV9SZV9uZXdfc3ViKSwgY2VudGVycyA9IDIpDQpicmVhc3RDYV9SZV9uZXcgPC0gYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIG11dGF0ZShrY2x1c3RfMl9zY2FsZSA9IGZhY3RvcihrY2x1c3RfazJfc2NhbGUkY2x1c3RlcikpDQoNCiMgVmlzdWFsaXplIHRoZSBuZXcgY2x1c3RlciBhc3NpZ25tZW50cw0Kc2NhbGVkQ2x1c3RlclBsb3QgPC0gZ2dwbG90KA0KICBicmVhc3RDYV9SZV9uZXcsDQogIGFlcygNCiAgICB4ID0gcGVyaW1ldGVyX21lYW4sDQogICAgeSA9IGBjb25jYXZlIHBvaW50c193b3JzdGAsDQogICAgY29sb3IgPSBrY2x1c3RfMiwNCiAgICB0ZXh0ID0gcGFzdGUoJ2RpYWdub3NpczogJywgZGlhZ25vc2lzKQ0KICApDQopICsNCiAgZ2VvbV9wb2ludCgpICsNCiAgdGhlbWVfY2xhc3NpYygpDQoNCmdncGxvdGx5KHNjYWxlZENsdXN0ZXJQbG90ICAsIHRvb2x0aXAgPSBjKCAidGV4dCIpKQ0KYGBgDQoNCg0KIyMjIENsdXN0ZXJpbmcgb24gbW9yZSB2YXJpYWJsZXMNCmBgYHtyfQ0KIyBTZWxlY3QgdGhlIHZhcmlhYmxlcyB0byBiZSB1c2VkIGluIGNsdXN0ZXJpbmcNCmJyZWFzdENhX1JlX25ld19zdWIyIDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBzZWxlY3QoYygyOjIxKSkNCg0KIyBMb29rIGF0IHN1bW1hcnkgc3RhdGlzdGljcyBvZiB0aGUgMyB2YXJpYWJsZXMNCnN1bW1hcnkoYnJlYXN0Q2FfUmVfbmV3X3N1YjIpDQpgYGANCg0KYGBge3J9DQpzZXQuc2VlZCgyNTMpDQprY2x1c3RfazJfYWxsdmFycyA8LSBrbWVhbnMoc2NhbGUoYnJlYXN0Q2FfUmVfbmV3X3N1YjIpLCBjZW50ZXJzID0gMikNCg0KYnJlYXN0Q2FfUmVfbmV3IDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBtdXRhdGUoa2NsdXN0X2syX2FsbHZhcnMgPSBmYWN0b3Ioa2NsdXN0X2syX2FsbHZhcnMkY2x1c3RlcikpDQoNCg0KYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICBjb3VudChkaWFnbm9zaXMsa2NsdXN0X2syX2FsbHZhcnMpDQpgYGANCg0KIyMjIEludGVycHJldGluZyB0aGUgY2x1c3RlcnMNCmBgYHtyfQ0KYnJlYXN0Q2FfUmVfbmV3ICU+JQ0KICAgIGdyb3VwX2J5KGtjbHVzdF9rMl9hbGx2YXJzKSAlPiUNCiAgICBzdW1tYXJpemUoYWNyb3NzKGMoMjoyMSksIG1lYW4pKQ0KYGBgDQoNCiMjIyBQaWNraW5nIGsNCmBgYHtyfQ0KIyBEYXRhLXNwZWNpZmljIGZ1bmN0aW9uIHRvIGNsdXN0ZXIgYW5kIGNhbGN1bGF0ZSB0b3RhbCB3aXRoaW4tY2x1c3RlciBTUw0KYnJlYXN0Q2FfUmVfbmV3X2NsdXN0ZXJfc3MgPC0gZnVuY3Rpb24oayl7DQogICAgIyBQZXJmb3JtIGNsdXN0ZXJpbmcNCiAgICBrY2x1c3QgPC0ga21lYW5zKHNjYWxlKGJyZWFzdENhX1JlX25ld19zdWIyKSwgY2VudGVycyA9IGspDQoNCiAgICAjIFJldHVybiB0aGUgdG90YWwgd2l0aGluLWNsdXN0ZXIgc3VtIG9mIHNxdWFyZXMNCiAgICByZXR1cm4oa2NsdXN0JHRvdC53aXRoaW5zcykNCn0NCg0KdGliYmxlKA0KICAgIGsgPSAxOjIwLA0KICAgIHRvdF93Y19zcyA9IHB1cnJyOjptYXBfZGJsKDE6MjAsIGJyZWFzdENhX1JlX25ld19jbHVzdGVyX3NzKQ0KKSAlPiUgDQogICAgZ2dwbG90KGFlcyh4ID0gaywgeSA9IHRvdF93Y19zcykpICsNCiAgICBnZW9tX3BvaW50KCkgKyANCiAgICBnZW9tX2xpbmUoKSsNCiAgICBsYWJzKHggPSAiTnVtYmVyIG9mIGNsdXN0ZXJzIix5ID0gJ1RvdGFsIHdpdGhpbi1jbHVzdGVyIHN1bSBvZiBzcXVhcmVzJykgKyANCiAgICB0aGVtZV9jbGFzc2ljKCkNCmBgYA0KDQojIyMgTm9ybWFsaXplZCB2YXJpYWJsZXMgY2x1c3RlcmluZyB2aXN1YWxpemFpdG9uIG9mIG9wdGltYWwgSw0KYGBge3J9DQojIFJ1biBrLW1lYW5zIGZvciBrID0gY2VudGVycyA9IDMNCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rMyA8LSBrbWVhbnMoYnJlYXN0Q2FfUmVfbmV3X3N1YiwgY2VudGVycyA9IDMpDQoNCiMgRGlzcGxheSB0aGUgY2x1c3RlciBhc3NpZ25tZW50cw0Ka2NsdXN0X2szJGNsdXN0ZXINCg0KIyBSdW4gay1tZWFucyBvbiB0aGUgKnNjYWxlZCogZGF0YSAoYWxsIHZhcmlhYmxlcyBoYXZlIFNEID0gMSkNCnNldC5zZWVkKDI1MykNCmtjbHVzdF9rM19zY2FsZSA8LSBrbWVhbnMoc2NhbGUoYnJlYXN0Q2FfUmVfbmV3X3N1YiksIGNlbnRlcnMgPSAzKQ0KYnJlYXN0Q2FfUmVfbmV3IDwtIGJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBtdXRhdGUoa2NsdXN0XzNfc2NhbGUgPSBmYWN0b3Ioa2NsdXN0X2szX3NjYWxlJGNsdXN0ZXIpKQ0KDQojIFZpc3VhbGl6ZSB0aGUgbmV3IGNsdXN0ZXIgYXNzaWdubWVudHMNCm5ld0NsdXN0ZXJLUGxvdCA8LSBnZ3Bsb3QoDQogIGJyZWFzdENhX1JlX25ldywNCiAgYWVzKA0KICAgIHggPSBwZXJpbWV0ZXJfbWVhbiwNCiAgICB5ID0gYGNvbmNhdmUgcG9pbnRzX3dvcnN0YCwNCiAgICBjb2xvciA9IGtjbHVzdF8zX3NjYWxlLA0KICAgIHRleHQgPSBwYXN0ZSgnZGlhZ25vc2lzOiAnLCBkaWFnbm9zaXMpDQogICkNCikgKw0KICBnZW9tX3BvaW50KCkgKw0KICB0aGVtZV9jbGFzc2ljKCkNCg0KZ2dwbG90bHkobmV3Q2x1c3RlcktQbG90ICAsIHRvb2x0aXAgPSBjKCAidGV4dCIpKQ0KYGBgDQoNCiMjIyBQZXJmb3JtIGNsdXN0ZXJpbmcgb24gbW9yZSB2YXJpYWJlbHMgd2l0aCBLPTMNCmBgYHtyfQ0Kc2V0LnNlZWQoMjUzKQ0Ka2NsdXN0X2szX2FsbHZhcnMgPC0ga21lYW5zKHNjYWxlKGJyZWFzdENhX1JlX25ld19zdWIyKSwgY2VudGVycyA9IDMpDQojd2l0aGluIGNsdXN0ZXJzIHN1IG9mIHNxdWFyZXMNCmtjbHVzdF9rM19hbGx2YXJzDQoNCmJyZWFzdENhX1JlX25ldyA8LSBicmVhc3RDYV9SZV9uZXcgJT4lDQogICAgbXV0YXRlKGtjbHVzdF9rM19hbGx2YXJzID0gZmFjdG9yKGtjbHVzdF9rM19hbGx2YXJzJGNsdXN0ZXIpKQ0KDQoNCmJyZWFzdENhX1JlX25ldyAlPiUNCiAgY291bnQoZGlhZ25vc2lzLGtjbHVzdF9rM19hbGx2YXJzKQ0KYGBgDQoNCiMjIyBJbnRlcnByZXRpbmcgdGhlIGNsdXN0ZXJzIHdpdGggaz0zDQpgYGB7cn0NCmJyZWFzdENhX1JlX25ldyAlPiUNCiAgICBncm91cF9ieShrY2x1c3RfazNfYWxsdmFycykgJT4lDQogICAgc3VtbWFyaXplKGFjcm9zcyhjKDI6MjEpLCBtZWFuKSkNCmBgYA==